package com.bn.feeds.processor;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.transaction.TransactionManager;

import com.bn.feedMaster.FeedProcessException;
import com.bn.feedMaster.FeedsTools;

import atg.dtm.TransactionDemarcation;
import atg.dtm.TransactionDemarcationException;
import atg.epub.project.Process;
import atg.nucleus.GenericService;
import atg.repository.MutableRepository;
import atg.repository.MutableRepositoryItem;
import atg.repository.Query;
import atg.repository.QueryBuilder;
import atg.repository.QueryExpression;
import atg.repository.RepositoryException;
import atg.repository.RepositoryItem;
import atg.repository.RepositoryView;
import atg.servlet.ServletUtil;
import atg.userprofiling.email.MessageContentProcessor;
import atg.userprofiling.email.TemplateEmailException;
import atg.userprofiling.email.TemplateEmailInfoImpl;
import atg.userprofiling.email.TemplateEmailSender;

public class ProductCategorizer extends GenericService {

	private static final String CLASS_CODE = "classCode";

	private static final String CLASS_CODES = "classCodes";
	
	private static final String DEPARTMENT_CODES = "departmentCodes";
	
	private static final String DEPARTMENT_CODE = "departmentCode";

	private static final String FIXED_CHILD_PRODUCTS = "fixedChildProducts";

	private static final String CATEGORY = "category";

	private static final String PRODUCT = "product";

	private static final String DISPLAY_NAME = "displayName";
	
	private boolean enabled;
	
	private boolean testProcess;

	/** The transaction manager. */
	private TransactionManager transactionManager;

	/** The feed tools. */
	private FeedsTools feedTools;

	private MutableRepository productCatalog;

	private String processName = "ProductCategoryMapping";
	
	 private boolean sendEmail = false;
	
	 /** The template email info. */
	 private TemplateEmailInfoImpl templateEmailInfo;
	    
	 /** The template email sender. */
	 private TemplateEmailSender templateEmailSender;
	
	RepositoryItem[] products =null;
	
	boolean unsiteProductCategorizerEnabled;
	
	private String testProductID;
	private String testCategoryID;
	
	private int batchCount;
	

	public void process() {
		if(!enabled){
			if (isLoggingDebug()) {
				logDebug("ProductCategorizer::> Not Enabled");
			}	
			return;
		}
		
		if (isLoggingDebug()) {
			logDebug("ProductCategorizer::>process()::>BEGIN::"
					+ processName.toUpperCase());
		}
		TransactionDemarcation td = null;
		StringBuilder errorsBuffer = new StringBuilder();
		Process process = null;
		TransactionManager tm = getTransactionManager();

		try {

			RepositoryItem[] categories;
			td = new TransactionDemarcation();
            boolean rollback = false;
			try {
				
					td.begin(tm, TransactionDemarcation.REQUIRES_NEW);
					if(testProcess){
						if(isLoggingDebug()){
							logDebug("**********Running TestProcess flow.....");
						}
						categories = new RepositoryItem[1];
						categories[0]=getCategory();	
					} else{
						categories = getAllCategories();	
					}
					
					if (categories != null) {
						Map<String, Set<RepositoryItem>> classCodeCategories = getClassCodeCategories(categories);
						Map<String, Set<RepositoryItem>> departmentCodeCategories = getDepartmentCodeCategories(categories);
						process = categorizeProducts(classCodeCategories,departmentCodeCategories,errorsBuffer);
						if (null != process) {
							saveCategories(categories);
							getFeedTools().advanceWorkflow(process, errorsBuffer);
						}
					}
					
					
					
			}catch (TransactionDemarcationException e) {
				rollback=true;
				logError("ProductCategorizer::run()"+e.getMessage());
			} catch (RepositoryException e) {
				rollback=true;
				logError("ProductCategorizer::run()"+e.getMessage());
			} finally {
                try {
                    if (td != null)
                        td.end(rollback);
                } catch (TransactionDemarcationException tde) {
                    if (isLoggingError()) {
                        logError( " :: Exception occcured while Ending transaction", tde);
                    }
                }
            }

		} catch (FeedProcessException e) {
			logError("ProductCategorizer::run()"+e.getMessage());
		} catch (IOException e) {
			logError("ProductCategorizer::run()"+e.getMessage());
		} 

		Map<String, String> feedsOutput = new HashMap<String, String>();
		feedsOutput.put("message", errorsBuffer.toString());
		/*try {
			sendCompleteEmail(feedsOutput);
		} catch (TemplateEmailException e) {
			if(isLoggingError()){
				logError("Exception while sending mail");
			}
		}*/
	}

	/**
	 * Send complete email.
	 * 
	 * @param feedsOutput
	 *            the feeds output
	 */
    private void sendCompleteEmail(Map<String, String> feedsOutput) throws TemplateEmailException{

        if (isSendEmail()){
            
            TemplateEmailInfoImpl templateInfo =getTemplateEmailInfo();
            MessageContentProcessor contentProcessor = templateInfo.getContentProcessor();
            String subject = templateInfo.getMessageSubject();
            String emailFrom = templateInfo.getMessageFrom();
            String templateURL =templateInfo.getTemplateURL();
            HashMap<String, Object> templateParams = new HashMap<String, Object>();
            templateParams.put("feedsOutput", feedsOutput.get("message"));
            templateParams.put("locale", ServletUtil.getCurrentRequest().getLocale());
            templateInfo.setTemplateURL(templateURL);
            templateInfo.setMessageFrom(emailFrom);
            templateInfo.setMessageSubject(subject);
            templateInfo.setTemplateParameters(templateParams);
            templateInfo.setContentProcessor(contentProcessor);
            ArrayList<String> emails = new ArrayList<String>();
            if (templateInfo.getMessageTo() != null) {
                emails.add(templateInfo.getMessageTo());
                if (isLoggingDebug()) logDebug("ArrayList<String> emails=....." + emails);
                try {
                    getTemplateEmailSender().sendEmailMessage(templateInfo, emails);
                }
                catch (TemplateEmailException e) {
                    if (isLoggingError()){
                        logError("Error Occurred while Sending emails " , e);
                    }
                } catch (Exception e) {
                    if (isLoggingError()){
                        logError("Error Occurred while Sending emails " , e);
                    }
                }
            }
        }
    }

	
	/**
	 * Method returns all the categories
	 * 
	 * @return
	 * @throws RepositoryException
	 */
	private RepositoryItem[] getAllCategories() throws RepositoryException {
		if (isLoggingDebug()) {
			logDebug("Reading all categories ...");
		}
		RepositoryView categoryView = productCatalog.getView(CATEGORY);
		QueryBuilder queryBuilder = categoryView.getQueryBuilder();
		Query getCategoriesQuery = queryBuilder.createUnconstrainedQuery();
		return categoryView.executeQuery(getCategoriesQuery);
	}

	/**
	 * Method returns all products
	 * 
	 * @return
	 * @throws RepositoryException
	 */
	private RepositoryItem[] getAllProducts() throws RepositoryException {
		
		if(testProcess){
			RepositoryItem[] items = new RepositoryItem[1];
			items[0]= getProduct();
			return items;
		}
		
		if(products== null){
			if (isLoggingDebug()) {
			logDebug("Reading all products ...");
		}
		
		RepositoryView categoryView = productCatalog.getView(PRODUCT);
		QueryBuilder queryBuilder = categoryView.getQueryBuilder();
		Query getCategoriesQuery = queryBuilder.createUnconstrainedQuery();
		products =  categoryView.executeQuery(getCategoriesQuery);
		}
		
		return products;
	}
	
	/**
	 * Method returns all products
	 * 
	 * @return
	 * @throws RepositoryException
	 */
	private RepositoryItem[] getUnsitedProducts() throws RepositoryException {
		
		if(testProcess){
			RepositoryItem[] items = new RepositoryItem[1];
			items[0]= getProduct();
			return items;
		}
		
		if (isLoggingDebug()) {
			logDebug("Reading unsited products ...");
		}
		
		
		RepositoryView categoryView = productCatalog.getView(PRODUCT);
		QueryBuilder queryBuilder = categoryView.getQueryBuilder();
		
		QueryExpression customerIdExpression = queryBuilder.createPropertyQueryExpression("parentCategories");
		Query productQuery = queryBuilder.createIsNullQuery(customerIdExpression);
		
		RepositoryItem[] products =  categoryView.executeQuery(productQuery);
		
		return products;
	}

	private Map<String, Set<RepositoryItem>> getClassCodeCategories(
			RepositoryItem[] categories) {
		Map<String, Set<RepositoryItem>> classCodeCategories = new HashMap<String, Set<RepositoryItem>>();
		for (RepositoryItem category : categories) {
			List<String> classCodes = (List<String>) category
					.getPropertyValue(CLASS_CODES);
			for (String classCode : classCodes) {
				if (!classCodeCategories.containsKey(classCode)) {
					classCodeCategories.put(classCode,
							new HashSet<RepositoryItem>());
				}
				classCodeCategories.get(classCode).add(category);
			}
		}
		return classCodeCategories;
	}
	
	private Map<String, Set<RepositoryItem>> getDepartmentCodeCategories(
			RepositoryItem[] categories) {
		Map<String, Set<RepositoryItem>> departmentCodeCategories = new HashMap<String, Set<RepositoryItem>>();
		for (RepositoryItem category : categories) {
			List<String> departmentCodes = (List<String>) category
					.getPropertyValue(DEPARTMENT_CODES);
			for (String departmentCode : departmentCodes) {
				if (!departmentCodeCategories.containsKey(departmentCode)) {
					departmentCodeCategories.put(departmentCode,
							new HashSet<RepositoryItem>());
				}
				departmentCodeCategories.get(departmentCode).add(category);
			}
		}
		return departmentCodeCategories;
	}

	private Process categorizeProducts(
			Map<String, Set<RepositoryItem>> classCodeCategories,Map<String, Set<RepositoryItem>> departmentCodeCategories,StringBuilder errorsBuffer)
			throws RepositoryException, FeedProcessException, IOException {
		Process process = null;
		RepositoryItem[] products = null;
		if(isUnsiteProductCategorizerEnabled()){
			products = getUnsitedProducts();
		} else {
			products = getAllProducts();
		}
		
		int count=0;
		
		if (products != null) {
			for (RepositoryItem product : products) {
				logInfo("Processing "+count +" of "+products.length);
				if(count>=batchCount){
					break;
				}
				if (product.getPropertyValue(CLASS_CODE) != null
						&& product.getPropertyValue(DEPARTMENT_CODE) != null) {
					String classCode = (String) product
							.getPropertyValue(CLASS_CODE);
					RepositoryItem departmentCodeItem = (RepositoryItem) product
							.getPropertyValue(DEPARTMENT_CODE);
					String departmentCode = departmentCodeItem
							.getRepositoryId();
					if (classCode != null
							&& classCodeCategories.containsKey(classCode)
							&& departmentCode != null
							&& departmentCodeCategories
									.containsKey(departmentCode)) {

						Set<RepositoryItem> targetCategories = classCodeCategories
								.get(classCode);
						Set<RepositoryItem> departmentCodeCategoriesSet = departmentCodeCategories
								.get(departmentCode);
						for (RepositoryItem targetCategory : targetCategories) {
							if (!departmentCodeCategoriesSet
									.contains(targetCategory)) {
								continue;
							}
							List<RepositoryItem> fixedChildProducts = (List<RepositoryItem>) targetCategory
									.getPropertyValue(FIXED_CHILD_PRODUCTS);
							if (!fixedChildProducts.contains(product)) {
								if (null == process) {
									process = getFeedTools().startProjectInBCC(
											errorsBuffer, processName);
								}
								
								if (null != process) {
									count=count+1;
									fixedChildProducts.add(product);
									if (isLoggingDebug()) {
										logDebug("Adding product '"
												+ product
														.getPropertyValue(DISPLAY_NAME)
												+ "' ("
												+ product.getRepositoryId()
												+ ") to "
												+ "'"
												+ targetCategory
														.getPropertyValue(DISPLAY_NAME)
												+ "' ("
												+ targetCategory.getRepositoryId()
												+ ")");
									}
								}
							}
							
						}
					}

				}
			}
		}
		return process;
	}

	private void saveCategories(RepositoryItem[] categories)
			throws RepositoryException {
		for (RepositoryItem category : categories) {
			getProductCatalog().updateItem((MutableRepositoryItem) category);
		}
	}
	
	/**
	 * This method is written for testing the flow, in local
	 * 
	 * @return
	 * @throws RepositoryException
	 */
	private RepositoryItem getCategory() throws RepositoryException{
		return productCatalog.getItem(getTestCategoryID(), CATEGORY);
	}
	
	private RepositoryItem getProduct() throws RepositoryException{
		return productCatalog.getItem(getTestProductID(), PRODUCT);
	}
	

	/**
	 * Gets the transaction manager.
	 * 
	 * @return the transaction manager
	 */
	public TransactionManager getTransactionManager() {
		return transactionManager;
	}

	/**
	 * Sets the transaction manager.
	 * 
	 * @param pTransactionManager
	 *            the new transaction manager
	 */
	public void setTransactionManager(TransactionManager pTransactionManager) {
		transactionManager = pTransactionManager;
	}

	/**
	 * @return the feedTools
	 */
	public FeedsTools getFeedTools() {
		return feedTools;
	}

	/**
	 * @param feedTools
	 *            the feedTools to set
	 */
	public void setFeedTools(FeedsTools feedTools) {
		this.feedTools = feedTools;
	}

	public MutableRepository getProductCatalog() {
		return productCatalog;
	}

	public void setProductCatalog(MutableRepository productCatalog) {
		this.productCatalog = productCatalog;
	}

	public boolean isEnabled() {
		return enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public boolean isTestProcess() {
		return testProcess;
	}

	public void setTestProcess(boolean testProcess) {
		this.testProcess = testProcess;
	}

	public String getTestProductID() {
		return testProductID;
	}

	public void setTestProductID(String testProductID) {
		this.testProductID = testProductID;
	}

	public String getTestCategoryID() {
		return testCategoryID;
	}

	public void setTestCategoryID(String testCategoryID) {
		this.testCategoryID = testCategoryID;
	}

	public boolean isSendEmail() {
		return sendEmail;
	}

	public void setSendEmail(boolean sendEmail) {
		this.sendEmail = sendEmail;
	}

	public TemplateEmailInfoImpl getTemplateEmailInfo() {
		return templateEmailInfo;
	}

	public void setTemplateEmailInfo(TemplateEmailInfoImpl templateEmailInfo) {
		this.templateEmailInfo = templateEmailInfo;
	}

	public TemplateEmailSender getTemplateEmailSender() {
		return templateEmailSender;
	}

	public void setTemplateEmailSender(TemplateEmailSender templateEmailSender) {
		this.templateEmailSender = templateEmailSender;
	}

	public boolean isUnsiteProductCategorizerEnabled() {
		return unsiteProductCategorizerEnabled;
	}

	public void setUnsiteProductCategorizerEnabled(
			boolean unsiteProductCategorizerEnabled) {
		this.unsiteProductCategorizerEnabled = unsiteProductCategorizerEnabled;
	}

	public int getBatchCount() {
		return batchCount;
	}

	public void setBatchCount(int batchCount) {
		this.batchCount = batchCount;
	}

}
